package require TclOO
package require http
namespace import ::oo::*

##This class is a container for coordinate that obtained from RCSB PDB databank.
class create PDB {
    #core utilities: get&process
    #fetch coordinate information from PDB databank.
    method fetch {pdbid {url http://www.rcsb.org/pdb/files/ }} {}
    ##process the data obtained from PDB
    method process {} {}
    #core utilites: get information about the peptide
    ##Get the coordinate for $model and $chain
    method getCoor { model chain } {}
    ##Get the CHARMM friendly PDB coordinate.
    method getCharmm { model chain } {}
    ##Return list of chains.
    method chains {} {}
    ##Return the lenght of chain ?.
    method chainlen {chain} {}
    ##Return the sequence of chain ?.
    method sequence {chain} {}
    ##Return the disulfide bounds.
    method ssbonds {} {}
    ##Return the number of NMR models contained in a PDB.
    method hasNmodel {} {}
    #setup options
    method het2atom  {} {}
    #helper
    method getChain { str } {}
    method getSeq {str} {}
    method getSSbond  {str } {}
    method getModel {str models} {}
}


# main utilities
# fetch PDB from internet
oo::define PDB method fetch {pdbid {url http://www.rcsb.org/pdb/files/ }} {
    my variable body
    set token [::http::geturl $url/${pdbid}.pdb]
    #puts [::http::status $token] 
    if {[string compare [::http::status $token] ok] ==0 } {
	set body [::http::data $token]
	if ![regexp {HEADER} $body]  {
	    puts "Wrong PDB id! got no data!"
	    error "Empty PDB body"
	}
	#TEST   puts $body
    } else {
	    puts [::http::error $token]
    }
}

## Process the raw data obtained from PDB.
oo::define PDB method process {} {
    my variable body
    my variable models
    my getSeq $body
    my getSSbond $body
    my getModel $body models
    foreach model [array names models] {
	foreach {chain coor } [my getChain $models($model)] {
	    set models($model,$chain) $coor
	}
    }
}

oo::define PDB method getCoor { model chain } {
    my variable models
    return $models($model,$chain)
}
oo::define PDB method getCharmm { model chain } {
    my variable TER
    set str [my getCoor $model $chain]
    set str [string map { "CD1 ILE" "CD  ILE" } $str]
    set str [string map { OXT OT2 } $str]
    set str [string map [list "O   $TER($chain)" "OT1 $TER($chain)"] $str]
    set out ""
    foreach line [split $str \n] {
	set heavyatom [lindex [regexp -all -inline -- {\S+} $line ] end]
	if {[string compare $heavyatom H] != 0} {		
	    append out $line\n
	}
    }
    return $out
}

oo::define PDB method chains {} {
    my variable sequence
    return [lsort [ array names sequence]]
}
oo::define PDB method chainlen {chain} {
    my variable sequence
    return [llength [regexp -inline -all -- {\S+} $sequence($chain)]]
}
oo::define PDB method sequence {chain} {
    my variable sequence
    return $sequence($chain)
}
oo::define PDB method ssbonds {} {
    my variable ssbond
    if [info exists ssbond] {
	return $ssbond
    } else {
	return {}
    }
}
oo::define PDB method hasNmodel {} {
    my variable models
    set chain [lindex [my chains] 0]
    return [llength [array names models *,$chain]]
}


# helper functions
oo::define PDB method getChain { str } {
    my variable TER
    array unset TER
    foreach line [split $str \n] {
	if [regexp {^(ATOM|HETATM)\s+(\d+)\s+(\w+)\s+(\w{3,4})\s*(\w+)\s+(\d+).*} $line match recordtype index atomtype resname chain resid ] {
	    if ![info exists Chain($chain)] { set Chain($chain) $match\n 
	    } else {
		if ![info exist TER($chain)] { append Chain($chain) $match\n }
	    }
	}
	if [regexp {^TER\s+\d+\s*((\w+)\s+(\w+)\s+(\d+)).*} $line match ter resname chain] {
	    set TER($chain) $ter
	}
    }
    return [array get Chain]
}
oo::define PDB method getSeq {str} {
    my variable sequence
    foreach line [split $str \n] {
	if [regexp {^SEQRES\s+\d+\s+(\w+)\s+(\d+)\s+(.*?)\s+$} $line match chain len seq] {
	    if [info exists sequence($chain)] {
		append sequence($chain) " $seq"
	    } else {
		set sequence($chain) $seq
	    }
	}
    }
}
oo::define PDB method getSSbond  {str } {
    my variable ssbond 
    foreach line [split $str \n] {
	if [regexp {^SSBOND\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+\d+\s+\d+.*} $line match num resn1 chain1 res1 resn2 chain2 res2] {
	    lappend ssbond "$chain1 $res1 $chain2 $res2"
	}
    }
}
oo::define PDB method getModel {str models} {
    upvar $models body
    #puts [regexp -all -inline {MODEL\s+\d+\s*\n(.*)ENDMDL*} $str]
    #return
    array unset body
    set model 0
    set readblock false
    foreach line [split $str \n] {
	if [regexp {MODEL\s+\d+} $line] { incr model; set body($model) "";set readblock true;continue }
	if $readblock {
	    if [regexp {ENDMDL} $line] { set readblock false
	    } else {
		append body($model) $line\n
	    }
	    
	}
    }
    if { $model <= 0 } {
	set body($model) $str
    }
}

oo::define PDB method het2atom  {} {
    my variable body
    set body [ string map { HETATM "ATOM  "} $body ] 
}
   
   
